/* Declarations for high-level IO functions.

   This file is part of khipu.

   khipu is free software: you can redistribute it and/or modify
   it under the terms of the GNU Lesser General Public License as published by
   the Free Software Foundation; either version 3 of the License, or
   (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU Lesser General Public License for more details.

   You should have received a copy of the GNU Lesser General Public License
   along with this program.  If not, see <https://www.gnu.org/licenses/>.  */

#ifndef __KP_IO__
#define __KP_IO__   1

#include "interp.hpp"
#include "stream.hpp"
#include "initop.hpp"
#include "str.hpp"
#include "function.hpp"
#include "symbol.hpp"
#include "array.hpp"
#include "utils/lazy.hpp"

KP_DECLS_BEGIN

struct reader
{
  interpreter *interp;
  valref pairs_valref;
  local_varobj<array> pairs;
  object stpairs[16];
  uint32_t pair_cnt;
  char stbuf[256];
  char *bufp;
  int bufcnt;
  int bufmax;
  int toktype;
  stream *src;
  uint32_t lineno = 0;
  int bq_level = 0;
  bool unquoted = false;
  package *ipkg;

  reader (interpreter *interp, object input, package *ipkg = nullptr);

  bool readable_p () const
    {
      return (!this->src->eos_p () && !this->src->err_p ());
    }

  void take ();
  void handle_sym (object pkg, object name);

  bool read_token (schar& ch, int digs);

  void expand ();

  object getlbl (object label) const;
  object* putlbl (object label);

  bool nextc (schar& ch);
  void push_ch (const schar& ch);
  uint32_t peek ();

  object read_sexpr ();
  object read_sexpr (object label);
  object read_list (object label);
  object read_bq (object label);
  object read_comma (object label);
  object read_array (object label);
  object read_table (object label);
  object read_tuple (object label);
  object read_bvector ();
  object read_str ();
  object read_char ();

  ~reader ();
};

KP_EXPORT object expand_str (interpreter *interp, object str);

KP_EXPORT object expand_bq (interpreter *interp, object form);

KP_EXPORT int xwrite (interpreter *interp, stream *strm,
  object obj, io_info& info);

inline int
xwrite (interpreter *interp, stream *strm, object obj)
{
  io_info info;
  return (xwrite (interp, strm, obj, info));
}

template <class ...Args>
struct sprintf_args
{
  local_varobj<string> sf;
  callvec<Args...> xargs;

  sprintf_args (const char *fmt, Args... args) : xargs (args...)
    {
      this->sf.local_init (fmt);
    }

  uint32_t operator() (interpreter *interp)
    {
      interp->push (this->sf.as_obj ());
      return (1 + this->xargs (interp));
    }
};

template <class ...Args>
sprintf_args<Args...> make_sprintf_args (const char *fmt, Args... args)
{
  return (sprintf_args<Args...> (fmt, args...));
}

#define KP_SPRINTF(Interp, ...)   \
  khipu::call_wvec ((Interp), khipu::p_fmt_str,   \
    [&] () { return (khipu::make_sprintf_args (__VA_ARGS__)); })

// Object (de)serialization.

enum
{
  PACK_NIL           = 0x74,
  PACK_EMPTY_ARRAY   = 0x75,
  PACK_EMPTY_BVECTOR = 0x76,
  PACK_EMPTY_STR     = 0x77,
  PACK_INT8          = 0x78,
  PACK_FIXINT        = 0x79,
  PACK_CHAR8         = 0x7a,
  PACK_REF_INT32     = 0x7b,
  PACK_REF_OBJ       = 0x7c,
  PACK_TYPESPEC      = 0x7d,
  PACK_END           = 0x7e,
};

struct pack_cache
{
  local_varobj<array> l_obj;
  object st_tab[256];
  valref ref;
  uint32_t n_elem;
  uint32_t n_old;
  bool evict;

  uint32_t size () const;
  object* data ();
  pack_cache (interpreter *interp);
  object get (interpreter *interp, object key);
  void put (interpreter *interp, object key, object val);
};

struct pack_info
{
  valref map;
  valref offset;
  lazy<pack_cache> pcache;
  pack_cache *cache;
  stream *bstream;
  const char *errmsg;

  pack_info (interpreter *interp, bool cache);

  void add_mapping (interpreter *interp, object key, object val);
  object get (interpreter *interp, object obj);
  void touch (interpreter *interp, int offset);

  object error (const char *msg)
    {
      this->errmsg = msg;
      return (UNBOUND);
    }

  ~pack_info ()
    {
      if (this->cache)
        destroy (this->cache);
    }

  struct eviction_guard
    {
      bool *oldp;

      eviction_guard (pack_info& info, bool use)
        {
          if (use && info.cache->evict)
            *(this->oldp = &info.cache->evict) = false;
          else
            this->oldp = nullptr;
        }

      ~eviction_guard ()
        {
          if (this->oldp)
            *this->oldp = true;
        }
    };
};

KP_EXPORT int64_t xpack (interpreter *interp, stream *strm,
  object obj, pack_info& info);

KP_EXPORT int64_t xpack (interpreter *interp, stream *strm,
  object obj, object *map, size_t nmap);

template <class ...Args>
inline int64_t xpack_with (interpreter *interp, stream *strm,
  object obj, Args... args)
{
  object tmp[] = { args..., 0 };
  return (xpack (interp, strm, obj, tmp, KP_NELEM (tmp) - 1));
}

inline int64_t xpack (interpreter *interp, stream *strm, object obj)
{
  return (xpack (interp, strm, obj, nullptr, 0));
}

KP_EXPORT object xunpack (interpreter *interp,
  stream *strm, pack_info& info);

inline object xunpack (interpreter *interp, stream *strm)
{
  pack_info info { interp, false };
  return (xunpack (interp, strm, info));
}

// Print a full backtrace starting from FRAME to stream STRMP.
KP_EXPORT void print_backtrace (interpreter *interp,
  uint32_t frame, stream *strmp, io_info& info);

// Safely write a raised object to a stream.
KP_EXPORT void write_exc (interpreter *interp, stream *strm,
  object exc, io_info& info);

KP_EXPORT init_op init_io;

KP_DECLS_END

#endif
